package proyectoia;

import IA.DistFS.Requests;
import IA.DistFS.Servers;
import aima.search.framework.GoalTest;
import aima.search.framework.HeuristicFunction;
import aima.search.framework.Problem;
import aima.search.framework.Search;
import aima.search.framework.SearchAgent;
import aima.search.framework.SuccessorFunction;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;

public abstract class StagedExperiment {

    protected int seed, currentStage, numStages, numIterations;
    protected List<DistFileSystemState>[] initialStates;
    protected List<SearchAgent>[] searchAgents;
    protected List<Long>[] runTimes;
    private Boolean initialized;
    private Boolean executed;
    
    public static final int GREEDY = 0;
    public static final int RANDOM = 1;
    public static final int EMPTY = 2;
    
    public static final int ACTIONS = 0;
    public static final int STATES = 1;
    
    public StagedExperiment(int seed, int nIterations) {
        this.seed = seed;
        numIterations = nIterations;
        numStages = 1;
        initialized = false;
        executed = false;
    }

    private void checkInitialized() throws Exception {
        if (!initialized) {
            throw new Exception("Can't run an uninitialized experiment.");
        }
    }

    private void checkExecuted() throws Exception {
        if (!executed) {
            throw new Exception("Experiment needs to be executed first.");
        }
    }

    public String stageName(int s) {
        return "Stage " + s;
    }
    
    public String stageDescription(int s) {
        return stageName(s);
//        String desc = stageName(s);
//        desc += "\n\tSearch: " + getSearch().toString();
//        desc += "\n\tSuccessor: " + getSuccessor().toString();
//        desc += "\n\tHeuristic: " + getHeuristic().toString();
//        return desc;
    }

    public void initialitze() {
        initialStates = new ArrayList[numStages];
        searchAgents = new ArrayList[numStages];
        runTimes = new ArrayList[numStages];
        initialized = true;
    }

    public void execute() throws Exception {
        checkInitialized();
        for (int s = 0; s < numStages; s++) {
            currentStage = s;
            runProblemIterations(numIterations);
        }
        executed = true;
        System.out.println("Execution finished.\n");
    }
    
    protected DistFileSystemState getLastState(DistFileSystemState state, SearchAgent agent) throws Exception {
        switch (getSearchResult()) {
            case ACTIONS:
                state.parseActionList(agent.getActions());
                return state;
            case STATES:
                return (DistFileSystemState) agent.getActions().get(agent.getActions().size()-1);
                        
        }
        return null;
    }
    
    private List<DistFileSystemState> getStateList(DistFileSystemState state, SearchAgent agent) throws Exception {
        List<DistFileSystemState> list = new ArrayList();
        switch (getSearchResult()) {
            case ACTIONS:
                list.add(new DistFileSystemState(state));
                for (Object o : agent.getActions()) {
                    state.parseAction((String) o);
                    list.add(new DistFileSystemState(state));
                }
                break;
            case STATES:
                list.add(state);
                for(Object o : agent.getActions()) {
                    list.add(new DistFileSystemState((DistFileSystemState) o));
                }
                break;
        }
        return list;
    }

    abstract int getNumServers();

    abstract int getNumReplications();

    abstract int getNumUsers();

    abstract int getNumRequests();

    /*
     * Returns the initial solution with whatever initial state used in the Experiment
     */
    abstract DistFileSystemState getInitialSolution(DistFileSystemState state);

    abstract Search getSearch();
    
    protected Integer getSearchResult() {
        return ACTIONS;
    }

    abstract SuccessorFunction getSuccessor();

    abstract GoalTest getGoalTest();

    abstract HeuristicFunction getHeuristic();

    /*
     * Returns a random state dependending on seed and the current
     * configuration of the experiment
     */
    protected DistFileSystemState getRandomState(int seed) throws Exception {
        Servers s = new Servers(getNumServers(), getNumReplications(), seed);
        Requests r = new Requests(getNumUsers(), getNumRequests(), seed);
        return new DistFileSystemState(s, r, getNumServers());
    }

    /*
     * Runs the problem once, and returns the execution time
     */
    protected void runProblem(DistFileSystemState state) throws Exception {
        System.out.print("Doing iteration... ");
        initialStates[currentStage].add(new DistFileSystemState(state));
        Problem p = new Problem(state, getSuccessor(), getGoalTest(), getHeuristic());
        Search s = getSearch();
        long initialTime = System.currentTimeMillis();
        SearchAgent agent = new SearchAgent(p, s);
        long totalTime = System.currentTimeMillis() - initialTime;
        searchAgents[currentStage].add(agent);
        runTimes[currentStage].add(totalTime);
        System.out.println(agent.getInstrumentation() + " time: " + totalTime);
    }

    /*
     * Runs the problem iteration times with random configurations 
     * and returns the average execution time
     */
    protected void runProblemIterations(int iterations) throws Exception {
        System.out.println(stageName(currentStage));
        initialStates[currentStage] = new ArrayList();
        searchAgents[currentStage] = new ArrayList();
        runTimes[currentStage] = new ArrayList();
        for (int i = 0; i < iterations; i++) {
            runProblem(getInitialSolution(getRandomState(this.seed + i)));
        }
    }
    
    public Integer getNumStages() {
        return numStages;
    }
    
    public Integer getNumIterations() {
        return numIterations;
    }
    
    public void printSlowestServerTime(String filename) throws Exception {
        FileWriter outFile = new FileWriter("data/" + filename + "-slowest-server-time.data");
        PrintWriter o = new PrintWriter(outFile);
        o.println("# Slowest server time");
        for (int s = 0; s < numStages; s++) {
            this.currentStage = s;
            o.println("# " + stageName(s));
            SummaryStatistics is = new SummaryStatistics();
            for (int i = 0; i < numIterations; i++) {
                is.addValue(initialStates[s].get(i).getSlowestServerTime());
            }
            System.out.println(stageName(s));
            System.out.println("\tTemps servidor més lent inici " + (int) is.getMean() + " amb " + (int) is.getStandardDeviation() + " stdev per " + numIterations + " iteracions.");
            SummaryStatistics ss = new SummaryStatistics();
            SummaryStatistics rt = new SummaryStatistics();
            for (int i = 0; i < numIterations; i++) {
                DistFileSystemState state = new DistFileSystemState(initialStates[s].get(i));
                DistFileSystemState last = getLastState(state, searchAgents[s].get(i));
                Integer time = last.getSlowestServerTime();
                ss.addValue(time);
                rt.addValue(runTimes[s].get(i));
                o.print(time + "\t");
            }
            o.println();
            System.out.println("\tTemps servidor més lent final " + (int) ss.getMean() + " amb " + (int) ss.getStandardDeviation() + " stdev per " + numIterations + " iteracions. " + (int) rt.getMean() + "ms amb " + (int) rt.getStandardDeviation() + " stdev en executar-se.");
        }
        o.close();
    }
    
    
    public void printSlowestServerGainTime(String filename) throws Exception {
        FileWriter outFile = new FileWriter("data/" + filename + "-slowest-gain-time.data");
        PrintWriter o = new PrintWriter(outFile);
        o.println("# Gain slowest time");
        for (int s = 0; s < numStages; s++) {
            o.println("# " + stageName(s));
            SummaryStatistics is = new SummaryStatistics();
            for (int i = 0; i < numIterations; i++) {
                is.addValue(initialStates[s].get(i).getSlowestServerTime());
            }
            System.out.println(stageName(s));
            System.out.println("\tTemps servidor més lent inici " + (int) is.getMean() + " amb " + (int) is.getStandardDeviation() + " stdev per " + numIterations + " iteracions.");
            SummaryStatistics ss = new SummaryStatistics();
            SummaryStatistics rt = new SummaryStatistics();
            for (int i = 0; i < numIterations; i++) {
                DistFileSystemState state = new DistFileSystemState(initialStates[s].get(i));
                DistFileSystemState last = getLastState(state, searchAgents[s].get(i));
                Integer time = last.getSlowestServerTime();
                ss.addValue(time);
                rt.addValue(runTimes[s].get(i));
                
                
            }
            for (int i = 0; i < numIterations; i++) {
                DistFileSystemState state = new DistFileSystemState(initialStates[s].get(i));
                DistFileSystemState last = getLastState(state, searchAgents[s].get(i));
                o.print(last.getSlowestServerTime() + "\t"+ss.getMean() + "\t");
            }
            o.println();
            System.out.println("\tTemps servidor més lent final " + (int) ss.getMean() + " amb " + (int) ss.getStandardDeviation() + " stdev per " + numIterations + " iteracions. " + (int) rt.getMean() + "ms amb " + (int) rt.getStandardDeviation() + " stdev en executar-se.");
        }
        o.close();
    }
    
    public void printServerTime(String filename) throws Exception {
        FileWriter outFile = new FileWriter("data/" + filename + "-slowest-server-time.data");
        PrintWriter o = new PrintWriter(outFile);
        o.println("# Slowest server time");
        System.out.println("Resultats per " + numIterations + " iteracions.");
        for (int s = 0; s < numStages; s++) {
            SummaryStatistics is = new SummaryStatistics();
            SummaryStatistics id = new SummaryStatistics();
            for (int i = 0; i < numIterations; i++) {
                is.addValue(initialStates[s].get(i).getTotalServerTime());
                id.addValue(initialStates[s].get(i).getServerTimeStandardDeviation());
            }
            System.out.println(stageName(s));
            System.out.print("\tTemps total inici " + (int) is.getMean() + " (" + (int) is.getStandardDeviation() + " stdev).");
            System.out.print(" Diferencia entre servers " + (int) id.getMean() + " (" + (int) id.getStandardDeviation() + " stdev).");
            System.out.println();
            SummaryStatistics tt = new SummaryStatistics();
            SummaryStatistics rt = new SummaryStatistics();
            SummaryStatistics sd = new SummaryStatistics();
            for (int i = 0; i < numIterations; i++) {
                DistFileSystemState state = new DistFileSystemState(initialStates[s].get(i));
                DistFileSystemState last = getLastState(state, searchAgents[s].get(i));
                tt.addValue(last.getTotalServerTime());
                sd.addValue(last.getServerTimeStandardDeviation());
                rt.addValue(runTimes[s].get(i));
                o.print(last.getTotalServerTime() + "\t");
            }
            o.println();
            System.out.print("\tTemps total final " + (int) tt.getMean() + " (" + (int) tt.getStandardDeviation() + " stdev).");
            System.out.print(" Diferencia entre servers " + (int) sd.getMean() + " (" + (int) sd.getStandardDeviation() + " stdev).");
            System.out.print(" Triga " + (int) rt.getMean() + "ms (" + (int) rt.getStandardDeviation() + " stdev) en executar-se.");
            System.out.println();
        }
        o.close();
    }

    public String getServerTimesEvolutionInfo(Integer stage, Integer iteration) throws Exception {
        checkExecuted();
        List<List<Integer>> serverTimesList = new ArrayList();
        // Collect server time data for every step of the algorithm
        DistFileSystemState state = new DistFileSystemState(initialStates[stage].get(iteration));
        SearchAgent search = searchAgents[stage].get(iteration);
        // Save list of server times for the first step
        serverTimesList.add(state.getServerTimesList());
        // Save list of server times for the rest
//        for (Object o : search.getActions()) {
//            String action = (String) o;
//            state.parseAction(action);
//            serverTimesList.add(state.getServerTimesList());
//        }
        for (DistFileSystemState s : getStateList(state, search)) {
            serverTimesList.add(state.getServerTimesList());
        }
        // Format the output data
        String data = "# Simulation, Stage " + stage + ", Iteration " + iteration + "\n";
        Integer numServers = state.numServers;
        Integer numSteps = serverTimesList.size();
        for (int server = 0; server < numServers; server++) {
            data += "# Server " + server + ":\n";
            for (int step = 0; step < numSteps; step++) {
                data += step + " " + serverTimesList.get(step).get(server) + "\n";
            }
            data += "\n";
        }
        return data;
    }
    
    public String getSlowestServerTimeInfo() throws Exception {
        // Collect data
        List<Integer>[] iniSlowestServerTimes = new ArrayList[numStages];
        List<Integer>[] endSlowestServerTimes = new ArrayList[numStages];
        for (int s = 0; s < numStages; s++) {
            iniSlowestServerTimes[s] = new ArrayList();
            endSlowestServerTimes[s] = new ArrayList();
            for (int i = 0; i < numIterations; i++) {
                DistFileSystemState state = new DistFileSystemState(initialStates[s].get(i));
                SearchAgent search = searchAgents[s].get(i);
                iniSlowestServerTimes[s].add(state.getSlowestServerTime());
                state.parseActionList(search.getActions());
                endSlowestServerTimes[s].add(state.getSlowestServerTime());
            }
        }
        // Format output
        String data = "";
        for (int s = 0; s < numStages; s++) {
            Integer sum = 0;
            for (int i = 0; i < numIterations; i++) {
                sum += endSlowestServerTimes[s].get(i);
                data += s + " " + iniSlowestServerTimes[s].get(i) + "\n";
                data += (s + 0.25) + " " + endSlowestServerTimes[s].get(i) + "\n";
            }
            data += (s + 0.5) + " " + sum / numIterations + "\n";
           
        }
        return data;
    }
}
